package com.gitee.sparta.core.config;

import com.gitee.sparta.api.SpartaTemplate;
import com.gitee.sparta.core.cluster.AbstractClusterFollower;
import com.gitee.sparta.core.cluster.AbstractClusterLeader;
import com.gitee.sparta.core.cluster.ClusterCommonCode;
import com.gitee.sparta.core.cluster.ClusterTransportFactory;
import com.gitee.sparta.core.cluster.balance.LoadBalance;
import com.gitee.sparta.core.cluster.balance.NodeLoadBalance;
import com.gitee.sparta.core.cluster.strategy.StrategyFactory;
import com.gitee.sparta.core.listener.LeaderEnableEventListener;
import com.gitee.sparta.core.listener.SingleServerEnableEventListener;
import com.gitee.sparta.core.listener.SpartaClosedListener;
import com.gitee.sparta.core.listener.SpartaRefreshEventListener;
import com.gitee.sparta.core.process.SpartaAnnotationBeanPostProcessor;
import com.gitee.sparta.core.process.SpartaThreadPoolFactory;
import com.gitee.sparta.core.process.feature.SpartaAcknowledgeFeature;
import com.gitee.sparta.core.process.feature.SpartaDistributedRenewFeature;
import com.gitee.sparta.core.process.feature.SpartaDistributedRetryFeature;
import com.gitee.sparta.core.process.feature.SpartaTaskDispatcherFeature;
import com.gitee.sparta.core.process.operate.AbstractTimeoutTabOperate;
import com.gitee.sparta.core.process.operate.DenyTabOperate;
import com.gitee.sparta.core.process.operate.MetadataTabOperate;
import com.gitee.sparta.core.process.operate.ReadableTabOperate;
import com.gitee.sparta.core.process.operate.redis.LuaAbstractTimeoutTabOperate;
import com.gitee.sparta.core.process.operate.redis.LuaDenyTabOperate;
import com.gitee.sparta.core.process.operate.redis.LuaMetadataTabOperate;
import com.gitee.sparta.core.process.operate.redis.LuaReadableTabOperate;
import com.gitee.sparta.core.process.workers.SpartaCarryWorker;
import com.gitee.sparta.core.process.workers.SpartaListenWorker;
import com.gitee.sparta.core.property.SpartaCoreProperties;
import com.gitee.sparta.core.tempalte.SpartaTemplateImpl;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Bean;

/**
 * sparta springboot 中自动配置类，加载核心 bean
 * 是 sparta 的核心入口启动类
 *
 * @author zyred
 * @since v 0.0.1
 */
@EnableConfigurationProperties(SpartaCoreProperties.class)
public class SpartaAutoConfiguration<V> {

    @Bean
    public RedissonClient redissonClient(SpartaCoreProperties properties) {
        Config config = properties.getConfig();
        config.setUseScriptCache(true);
        return Redisson.create(config);
    }

    @Bean
    public SpartaAnnotationBeanPostProcessor spartaSpringBeanPostProcessor() {
        return new SpartaAnnotationBeanPostProcessor();
    }

    @Bean
    @ConditionalOnMissingBean(ReadableTabOperate.class)
    public ReadableTabOperate<V> readableTabOperate(SpartaCoreProperties properties, RedissonClient redissonClient) {
        return new LuaReadableTabOperate<>(properties, redissonClient);
    }

    @Bean
    @ConditionalOnMissingBean(MetadataTabOperate.class)
    public MetadataTabOperate<V> metadataTabOperate(RedissonClient redissonClient,
                                                    SpartaCoreProperties properties) {
        return new LuaMetadataTabOperate<>(redissonClient, properties);
    }

    @Bean
    public SpartaDistributedRenewFeature<V> spartaDistributedRenewFeature(SpartaCoreProperties properties,
                                                                          ReadableTabOperate<V> readableTabOperate) {
        return new SpartaDistributedRenewFeature<>(properties, readableTabOperate);
    }

    @Bean
    @ConditionalOnMissingBean(DenyTabOperate.class)
    public DenyTabOperate<V> denyTabOperate(SpartaCoreProperties properties, RedissonClient redissonClient) {
        return new LuaDenyTabOperate<>(properties, redissonClient);
    }

    @Bean
    public SpartaAcknowledgeFeature<V> spartaAcknowledgeFeature(SpartaCoreProperties properties,
                                                                DenyTabOperate<V> denyTabOperate,
                                                                ReadableTabOperate<V> readableTabOperate,
                                                                SpartaDistributedRenewFeature<V> renewFeature) {
        return new SpartaAcknowledgeFeature<>(properties, denyTabOperate, readableTabOperate, renewFeature);
    }

    @Bean
    public SpartaThreadPoolFactory spartaThreadPoolFactory(SpartaCoreProperties properties) {
        return new SpartaThreadPoolFactory(properties);
    }

    @Bean
    public SpartaDistributedRetryFeature<V> spartaDistributedRetryFeature(SpartaCoreProperties properties,
                                                                          ReadableTabOperate<V> readableTabOperate) {
        return new SpartaDistributedRetryFeature<>(properties, readableTabOperate);
    }

    @Bean
    public SpartaListenWorker<V> spartaListenWorker(SpartaCoreProperties properties,
                                                    ReadableTabOperate<V> readableTabOperate,
                                                    MetadataTabOperate<V> metadataTabOperate,
                                                    SpartaThreadPoolFactory spartaThreadPoolFactory,
                                                    SpartaDistributedRetryFeature<V> retryFeature,
                                                    SpartaAcknowledgeFeature<V> spartaAcknowledgeFeature) {
        return new SpartaListenWorker<>(
                properties,
                readableTabOperate,
                metadataTabOperate,
                spartaThreadPoolFactory,
                retryFeature,
                spartaAcknowledgeFeature
        );
    }

    @Bean
    public SpartaTaskDispatcherFeature<V> spartaCarryListenerNotifierFeature(SpartaCoreProperties properties,
                                                                             SpartaListenWorker<V> spartaListenWorker,
                                                                             MetadataTabOperate<V> metadataTabOperate,
                                                                             SpartaThreadPoolFactory spartaThreadPoolFactory) {
        return new SpartaTaskDispatcherFeature<>(properties, spartaListenWorker,
                metadataTabOperate, spartaThreadPoolFactory);
    }

    @Bean
    @ConditionalOnMissingBean(AbstractTimeoutTabOperate.class)
    public AbstractTimeoutTabOperate<V> timeoutTabOperate(RedissonClient client,
                                                          SpartaCoreProperties properties) {
        return new LuaAbstractTimeoutTabOperate<>(properties, client);
    }

    @Bean
    public SpartaCarryWorker<V> spartaCarryWorker(LoadBalance loadBalance,
                                                  RedissonClient redissonClient,
                                                  SpartaCoreProperties spartaCoreProperties,
                                                  AbstractTimeoutTabOperate<V> timeoutTabOperate,
                                                  SpartaTaskDispatcherFeature<V> notifierFeature) {
        return new SpartaCarryWorker<>(loadBalance, redissonClient, spartaCoreProperties,
                timeoutTabOperate, notifierFeature);
    }


    @Bean
    @ConditionalOnProperty(name = "spring.sparta.standalone", havingValue = "true")
    public LeaderEnableEventListener<V> leaderEnableEventListener(SpartaCarryWorker<V> spartaCarryWorker) {
        return new LeaderEnableEventListener<>(spartaCarryWorker);
    }

    @Bean
    @ConditionalOnProperty(name = "spring.sparta.standalone", havingValue = "false", matchIfMissing=true)
    public SingleServerEnableEventListener<V> singleServerEnableEventListener(
            SpartaCarryWorker<V> spartaCarryWorker) {
        return new SingleServerEnableEventListener<>(spartaCarryWorker);
    }

    @Bean
    public SpartaTemplate<V> spartaTemplate(SpartaCoreProperties properties,
                                            DenyTabOperate<V> denyTabOperate,
                                            AbstractTimeoutTabOperate<V> timeoutTabOperate,
                                            MetadataTabOperate<V> metadataTabOperate) {
        return new SpartaTemplateImpl<>(properties, denyTabOperate, timeoutTabOperate, metadataTabOperate);
    }

    @Bean
    public SpartaClosedListener<V> spartaClosedListener(RedissonClient redissonClient,
                                                        SpartaCoreProperties properties,
                                                        AbstractTimeoutTabOperate<V> timeoutTabOperate,
                                                        SpartaCarryWorker<V> spartaCarryWorker,
                                                        SpartaListenWorker<V> spartaListenWorker) {
        return new SpartaClosedListener<>(redissonClient, properties, timeoutTabOperate,
                spartaCarryWorker, spartaListenWorker);
    }

    @Bean
    public SpartaRefreshEventListener spartaStartedEventListener(LoadBalance loadBalance,
                                                                 RedissonClient redissonClient,
                                                                 SpartaCoreProperties properties,
                                                                 ApplicationEventPublisher eventPublisher) {
        return new SpartaRefreshEventListener(loadBalance, redissonClient, properties, eventPublisher);
    }

    @Bean
    @ConditionalOnMissingBean(SingleServerEnableEventListener.class)
    public AbstractClusterLeader abstractClusterLeader(ApplicationEventPublisher publisher,
                                                       LoadBalance loadBalance,
                                                       RedissonClient redissonClient,
                                                       SpartaCoreProperties properties,
                                                       ApplicationContext context,
                                                       ClusterCommonCode ccc) {

        DefaultListableBeanFactory beanFactory = (DefaultListableBeanFactory)
                context.getAutowireCapableBeanFactory();

        ClusterTransportFactory.Combination builder = ClusterTransportFactory
                .builder(publisher, properties, redissonClient, loadBalance, ccc);

        // 手动向 ioc 中注入bean
        AbstractClusterFollower follower = builder.getFollower();

        String beanName = follower.getClass().getSimpleName();
        char[] beanCharts = beanName.toCharArray();
        beanCharts[0] += 32;
        beanName = new String(beanCharts);

        beanFactory.registerSingleton(beanName, follower);

        StrategyFactory.init(redissonClient, loadBalance, properties, follower);
        return builder.getLeader();
    }


    @Bean
    @ConditionalOnMissingBean(LoadBalance.class)
    public LoadBalance nodeLoadBalance() {
        return NodeLoadBalance.getNodeLoadBalanceInstance();
    }

    @Bean
    @ConditionalOnMissingBean(SingleServerEnableEventListener.class)
    public ClusterCommonCode clusterCommonCode (RedissonClient client,
                                                LoadBalance loadBalance,
                                                SpartaCoreProperties properties,
                                                ApplicationEventPublisher publisher) {
        return new ClusterCommonCode(client, loadBalance, properties, publisher);
    }
}
